package com.example.common.filter;

import cn.hutool.json.JSONUtil;
import com.example.common.constant.SysConst;
import com.example.common.enums.HttpResponseEnum;
import com.example.common.utils.FlowUtils;
import com.example.common.utils.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 限流控制过滤器
 * 防止用户高频请求接口，借助Redis进行限流
 */
@Slf4j
@Component
@Order(SysConst.ORDER_FLOW_LIMIT)
public class FlowLimitingFilter extends HttpFilter {

    @Value("${flow-limit.api.limit}")
    int limit;

    @Value("${flow-limit.api.period}")
    int period;

    @Value("${flow-limit.api.lock}")
    int lock;

    @Resource
    private FlowUtils utils;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String address = request.getRemoteAddr();
        if (!tryCount(address)) {
            this.writeBlockMessage(response);
        } else {
            chain.doFilter(request, response);
        }
    }

    /**
     * 尝试对指定IP地址请求计数，如果被限制则无法继续访问
     *
     * @param address 请求IP地址
     * @return 是否操作成功
     */
    private boolean tryCount(String address) {
        synchronized (address.intern()) {
            if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(SysConst.FLOW_LIMIT_LOCK + address)))
                return false;
            String counterKey = SysConst.FLOW_LIMIT_COUNTER + address;
            String blockKey = SysConst.FLOW_LIMIT_LOCK + address;
            return utils.limitPeriodCheck(counterKey, blockKey, lock, limit, period);
        }
    }

    /**
     * 为响应编写拦截内容，提示用户操作频繁
     *
     * @param response 响应
     * @throws IOException 可能的异常
     */
    private void writeBlockMessage(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.setContentType("application/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.write(JSONUtil.toJsonStr(ResponseResult.fail(HttpResponseEnum.RESTRICT_ACCESS_ERROR)));
    }
}
